/*
 * RequestHelper.java
 *
 * Created on August 28, 2005, 11:00 PM
 *
 * To change this basis, choose Tools | Options and locate the basis under
 * the Source Creation and Management node. Right-click the basis and choose
 * Open. You can then make changes to the basis in the Source Editor.
 */

package com.xucia.jsponic.remote;



import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.security.auth.login.LoginException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import com.xucia.jsponic.JsponicRuntime;
import com.xucia.jsponic.data.GlobalData;
import com.xucia.jsponic.data.Identification;
import com.xucia.jsponic.data.ObjectId;
import com.xucia.jsponic.data.Persistable;
import com.xucia.jsponic.data.PersistableList;
import com.xucia.jsponic.remote.ClientConnection.IndividualRequest;
import com.xucia.jsponic.security.User;
import com.xucia.jsponic.security.UserSecurity;
/**
 *
 * @author Kris Zyp
 * RequestHelper performs the necessary request and response setup to facilitate JSPON objects being transferred
 * to and from the server and JSPON client.
 */
public class RequestHelper extends JsponReceiver {
	public static final int DEFAULT_LOGIN_TIME_LENGTH = 604800; // one week, TODO: get this value from the database

	ClientConnection connection;
	/** Creates a new instance of RequestHelper */
	HttpServletRequest request;
	HttpServletResponse response;

	static boolean multiLingual = false;
	/**
	 * This takes the request parameters that come from the browser and string values and converts them to
	 * objects and primitives using JSPON conversion
	 * @return A map of the parameters with their correct objects as values
	 */
	public Map<String,Object> getParametersAsObjects() {
        Map<String,Object> parameters = new HashMap<String,Object>();
        Enumeration<String> parameterNames = request.getParameterNames();
        String name;
        try {
	        while (parameterNames.hasMoreElements()) {
	            name = parameterNames.nextElement();
	            parameters.put(name,convertParameterValueToObject(new String(request.getParameter(name).getBytes("ISO-8859-1"),"UTF8")));
	        }
        }
        catch (UnsupportedEncodingException e) {
        	throw new RuntimeException(e);
        }
        
        return parameters;
	}
	/**
	 * This converts a string value to an object using JSPON conversion. For instance the string:<br>
	 * <code>{"id":"3"}</code><br>
	 * Should return the object with id of 3.
	 * @param parameterValue string JSPON representation of the object
	 * @return The object converted from JSPON
	 */
	public Object convertParameterValueToObject(String parameterValue) {
		return convertJsponStringToObject(parameterValue);
	}
	Object convertWithKnownId(String jsponString,Identification<? extends Object> id) {
		try {
			Object value = new JSONTokener(jsponString).nextValue();
			if (value instanceof JSONArray) {
				JSONArray array = (JSONArray) value;
				List targetList = (List) id.getTarget();
				targetList.clear();
		    	for (int i =0; i < array.length(); i++) {
					value = array.get(i);
					value = convertJsponStringToObject(value.toString()); // TODO: It shouldn't need to go back and forth from a string
		    		targetList.add(value);
		    	}
		    	return targetList;
			}
			if (value instanceof JSONObject)
				return convertIdIfNeeded(idFromJSPONObject((JSONObject) value,id instanceof ObjectId ? (ObjectId) id : null));
			else
				return convertJsponStringToObject(jsponString);
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * This takes an object and returns a JSPON string representation
	 * @param returnValue The object to output
	 * @return JSPON string representation
	 */
	public String outputReturnObject(Object returnValue)  {
        IndividualRequest requestHandler = ClientConnection.getCurrentObjectResponse();
    	return requestHandler.getValueString(returnValue, true);
        //requestHandler.outputWaitingData();
        
	}
	public String outputWaitingResponse() throws IOException {
        IndividualRequest requestHandler = ClientConnection.getCurrentObjectResponse();
        return requestHandler.outputWaitingData();

	}
	public RequestHelper(HttpServletRequest request,HttpServletResponse response) {
		this.request = request;
		this.response = response;
		setupRequestAndResponse();
	}
	private void setupRequestAndResponse(){
		//if WML response.setContentType("text/vnd.wap.wml");
		HttpSession session = request.getSession();
		String sessionString = request.getParameter("Session");
		sessionString = (sessionString == null ? "" : sessionString) + "clientSession"; 
		connection =(ClientConnection) session.getAttribute(sessionString);
		
		if (connection == null) { 
			connection = new ClientConnection();
			session.setAttribute(sessionString,connection);            
			session.setAttribute("clientSession",connection);            
			connection.setSession(session);
			//if (BrowserAdapter.getBrowserType(request) == BrowserAdapter.BrowserType.SAFARI)
				//connection.isSafari = true;
			if (request.getParameter("mode") != null)
				connection.mode=request.getParameter("mode");
			if ("true".equals(request.getParameter("debug")))
				connection.setDebugMode(true);
		}
		ClientConnection.registerThisConnection(connection.getIndividualRequest(request));

        connection.prefetchEnabled = !"server".equals(request.getSession().getAttribute("mode"));
        connection.safeMode = "true".equals(request.getParameter("safe"));
		String authorization = request.getHeader("Auth"); // in case the client specificies a session
		if (authorization == null)
			authorization = request.getHeader("Authorization"); // in case the client specificies a session
		if (authorization == null)
			authorization = request.getParameter("Auth"); // in case the client specificies a session
		if (authorization == null)
			authorization = request.getParameter("Authorization"); // in case the client specificies a session
		User user = (User) session.getAttribute(User.class.toString());
		//Cookie[] cookies = request.getCookies();
		//TODO: Should we check to make sure the provided ticket matches the cookie?
//		user = null;		
		//RequestHelper.registerWebsite(request);
		if (authorization != null) {
			if (user == null || !authorization.equals(connection.getAuthorization())) 						
				try {  
					String[] tokens = authorization.split(":");
					String username = tokens[0];
					String password = tokens[1];
					user = UserSecurity.authenticate(username, password);
					String newPassword = user.getCurrentTicket();					
//					TODO: This may be considered a security hazard:
					if (!newPassword.equals(password)) {
						Cookie cookie = new Cookie("Auth",username + ":" + password);
						cookie.setMaxAge(DEFAULT_LOGIN_TIME_LENGTH); 
						cookie.setPath("/");
						response.addCookie(cookie);
					}
					connection.setAuthorization(authorization);
				}
				catch (LoginException e) { // if it fails silently go to the public user
					//System.err.println("Cookie login failed for " + ticket + " " + e.getMessage());
				}
				catch (IndexOutOfBoundsException e) {
					
				}
		}
		if (authorization == null || user == null) 
			user = new User();
		else
			session.setAttribute(User.class.toString(),user);

		user.registerThisThread();


		if (multiLingual) {
			response.setHeader("Vary","Accept-Language");
		}

	}
		
	
	
	
	
	
	void clearSessionAttributes(HttpSession session) {
		System.err.println("The data was changed we should be reloading all parameters");
		Enumeration enumer = session.getAttributeNames();
		while (enumer.hasMoreElements())
			session.removeAttribute((String) enumer.nextElement());
		
	}
	ClientConnection getClientConnection() {
		return connection;
	}
	
	boolean pageNotFound = false;
	public HttpServletRequest getRequest() {
		return request;
	}
	public HttpServletResponse getResponse() {
		return response;
	}
	@Override
	public Identification<? extends Object> idFromJSPONObject(JSONObject object) throws JSONException {
		return idFromJSPONObject(object, null);
	}
	public Identification<? extends Object> idFromJSPONObject(JSONObject object, ObjectId targetId) throws JSONException {
		//TODO: This needs be rearranged so that when you do a put (specifically an alteration), that we use the object 
		// returned by the put instead of what the id indicates, because it is possible for a aliasId to indicate that we 
		// should use a new object, when really we should use an existing object (from the childMods list)
    	String key;
    	Persistable target = null;
		Date changesSince=null;
		if (object.has("update") && object.get("update") instanceof JSONObject && object.getJSONObject("update").has("changesSince")) {
			String value = object.getJSONObject("update").getString("changesSince");
			changesSince = new Date(Long.parseLong(value.substring(1,value.length()-1))); // handle dates			
		}
    	if (object.has("$ref")) {
    		
    		String id = (String) object.get("$ref");
    		if (id.startsWith("../"))
    			id = id.substring(3);
    		else
    			id = path + id;
    		return Identification.idForString(id);
    	}
    	if (object.has("id")) {
    		String id = (String) object.remove("id");
    		if (id.startsWith("../"))
    			id = id.substring(3);
    		else
    			id = path + id;
    		if (id.startsWith("client/"))
    			target = ClientConnection.getCurrentObjectResponse().getConnection().clientSideObject(id,createInitialObject(object));
    		else {
    			targetId = ObjectId.idForString(id);
    		}
    	}
    	if (targetId == null) {
    		if (target == null)
    			target = createInitialObject(object);
    	}
    	else {
    		target= targetId.getTarget();
    	}
    	/*if (GlobalData.CHILDMODS_FIELD.equals(childModOriginalId))
    		target.isChildModsFlag = true;*/
    	for (String oldKey : target.keySet()) {
    		if (!object.has(oldKey) && !oldKey.startsWith(":") && !oldKey.equals("parent"))
    			target.delete(oldKey);
    	}
    	for (Iterator iter = object.keys(); iter.hasNext();) {
    		key = (String) iter.next();  // TODO: This needs to be limited to alteration lists, so we don't get a conflict with fields that start with c$.  This may need to be identified on the client side
    		Object value = object.get(key);
    		if (key.startsWith("client/")) // This is a client id alteration which needs be changed a 
    		{
    			key = ClientConnection.getCurrentObjectResponse().getConnection().clientSideObject(key,object.has("array") ? 
    						JsponicRuntime.newArray() : JsponicRuntime.newObject((ObjectId) idOrValueFromJSON(object.remove(GlobalData.BASIS_FIELD)))).getId().toString();
    		}
    		/*String valueModOriginal = null;
    		if (GlobalData.CHILDMODS_FIELD.equals(key))
    			valueModOriginal = GlobalData.CHILDMODS_FIELD;

    		if (target!=null && (GlobalData.CHILDMODS_FIELD.equals(childModOriginalId) || target.isChildMods()) && !GlobalData.PARENT_FIELD.equals(key))
    			valueModOriginal = key;*/
/*    		if ("update".equals(key)) {
    			if ("delete".equals(value))
    				return getErasureEntity();
    			if (value instanceof JSONObject)
    				updateList(target,(JSONObject) value);
    		}
    		else {*/
    			value = idOrValueFromJSON(value);
    			Logger.getLogger(RequestHelper.class.toString()).info("putting " + key + " value " + value + " for " + target);
	    		if (key.equals(FUNCTION_CODE_KEY)) {
	    			value = functionCompression((String) value);
	    			}
	    		if ("array".equals(key)) {
					//target.clear();
/*					if (Templates.findTemplate("groupBasis").isBasisOf(target)) {
						for (DataObject oldMember : (ArrayObjectCapable<DataObject>) target)
							if (!((List) value).contains(oldMember))
								User.calculateMembership(oldMember,null);
						replaceList((ArrayObjectCapable) target,(List) value);
						User.recalculateGroupMembership(target); // TODO: This goes through every member, it could be optimized to just do the new users
					}
					else*/
	    			if (value == getErasureEntity()) {
	    				throw new RuntimeException("Can not make a list not be a list any longer");
						//target.removeField(GlobalData.CONTAINS_REPEATING_IDENTIFIER);
					}
					else {
						replaceList((PersistableList) target,(List) value);
					}
				}
	    		else if (target != null) {
					/*for (Object entry : target.entrySet()) { // Find the correct key, the javascript may have a javascriptted name (without spaces, camelized) for the field, and we need to find the correct key for it
						if (ClientConnection.makeIntoJSSymbol(((Entry<String,Object>)entry).getKey())
								.equals(key)) {
							key = ((Entry<String,Object>)entry).getKey();
						}
					}*/
		    			if (value == getErasureEntity())
		    				target.delete(key);
		    			else if (GlobalData.BASIS_FIELD.equals(key))
		    				target.setPrototype((Persistable) convertIdIfNeeded(value));
		    			else
		    				target.set(key,convertIdIfNeeded(value));
	    		}
    		//}
    	}
/*		if (parentValue != null) // we will do it at the end so that if it is an append list entry it can be done without security problems
			target.set(GlobalData.PARENT_FIELD, parentValue);*/
    	return target.getId();
	}
	
}
